package snippets.utils;

import info.bliki.wiki.BlikiConverter;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import snippets.cse524.activeDates.WeigthtedActivity;

public final class Utils {

	public static final String CUSTOM_WIKI_LINK_REGEX = "##TEXT##(.*?)##LINK##(.*?)##";
	public static final Pattern CUSTOM_WIKI_LINK_PATTERN = Pattern
			.compile(CUSTOM_WIKI_LINK_REGEX);

	private interface WeightFromPeriodCount {
		double getWeight(int beginYear, int endYear, double mentions,
				double totalMentions);
	}

	private static Map<String, WeightFromPeriodCount> weightFuncs = new HashMap<String, WeightFromPeriodCount>();
	private static Map<Integer, Double> yearWeights = new HashMap<Integer, Double>();

	public static final String readFile(File file) throws IOException {
		FileInputStream stream = new FileInputStream(file);
		try {
			FileChannel fc = stream.getChannel();
			MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0,
					fc.size());
			/* Instead of using default, pass in a decoder. */
			return Charset.defaultCharset().decode(bb).toString();
		} finally {
			stream.close();
		}
	}

	public static Map<String, String> readCanonicalNames(InputStream input)
			throws FileNotFoundException {
		Map<String, String> canonicalNames = new HashMap<String, String>();
		Scanner in = new Scanner(input);

		while (in.hasNextLine()) {
			String line = in.nextLine().trim();

			String[] parts = StringUtils.split(line);

			if (parts.length != 3) {
				System.err.println("Invalid canonical name line : "
						+ StringUtils.join(parts, " ## "));
				continue;
			}

			String left = StringUtils.join(StringUtils.split(parts[0], '_'),
					' ').toLowerCase();

			canonicalNames.put(left, parts[1]);
		}

		return canonicalNames;
	}
	
	public static Map<Integer, String> readSources(InputStream input) {
		Map<Integer, String> sources = new HashMap<Integer, String>();
		Scanner in = new Scanner(input);

		while (in.hasNextLine()) {
			String line = in.nextLine().trim();

			String[] parts = StringUtils.split(line);

			if (parts.length != 2) {
				System.err.println("Invalid sources line : " + line);
				continue;
			}

			sources.put(Integer.parseInt(parts[0]), parts[1]);
		}

		return sources;
		
	}
	
	public static final String trimPlusPalindromes(String input) {
		StringBuffer result = new StringBuffer();

		int loc;
		while ((loc = input.indexOf('+')) != -1) {
			int gap = 1;

			int maxGap = 0;
			while (loc - gap >= 0 && loc + gap < input.length()) {
				if (input.substring(loc - gap, loc).equals(
						input.substring(loc + 1, loc + gap + 1))) {
					maxGap = gap;
				}

				gap += 1;
			}

			// We exceeded a valid gap above
			if (maxGap == 0) {
				gap = 0;
			} else {
				gap = maxGap;
			}

			String part = input.substring(0, loc);
			result.append(part);

			input = (gap == 0 ? " " : "") + input.substring(loc + gap + 1);
		}

		result.append(input);
		return result.toString();
	}

	public static String cleanHtml(String input) {
		input = StringEscapeUtils.unescapeHtml4(input);
		input = input.replaceAll("\\<[^\\>]*?\\>", "");
		return input;
	}

	public static String normalizeName(String input) {
		Set<String> parts = new HashSet<String>();

		for (String part : StringUtils.split(input)) {
			if (part.length() > 1) {
				parts.add(part);
			}
		}

		List<String> lparts = new ArrayList<String>(parts);

		Collections.sort(lparts);

		return StringUtils.join(lparts, " ");
	}

	public static IntervalEvaluator getThresholdedIntervalEvaluator(
			final double threshold) {
		return new IntervalEvaluator() {

			@Override
			public Pair<Integer, Integer> getInterval(
					Map<Integer, Double> yearCounts) {
				List<Double> thresholds = new ArrayList<Double>();
				thresholds.add(threshold);
				Map<Double, Pair<Integer, Integer>> intervals = getMinimumIntervalsForThresholds(
						thresholds, yearCounts);

				if (!intervals.containsKey(threshold))
					return null;

				return intervals.get(threshold);
			}
		};
	}

	public static IntervalEvaluator getFractionInternalEvaluator(
			final double power) {
		return new IntervalEvaluator() {

			@Override
			public Pair<Integer, Integer> getInterval(
					Map<Integer, Double> yearCounts) {

				List<WeigthtedActivity> activities = weightedPeriodByMethod(
						yearCounts, new WeightFromPeriodCount() {

							@Override
							public double getWeight(int beginYear, int endYear,
									double mentions, double totalMentions) {
								return 1.0d / Math.pow(endYear - beginYear,
										power);
							}
						});

				if (activities == null)
					return null;

				// NOTE: the funciton guarantees that it wont reurn an empty
				// list;
				assert (!activities.isEmpty());

				return new ImmutablePair<Integer, Integer>(activities.get(0)
						.getBeginYear(), activities.get(0).getEndYear());
			}
		};
	}

	public static Map<Double, Pair<Integer, Integer>> getMinimumIntervalsForThresholds(
			Iterable<Double> thresholds, Map<Integer, Double> yearCounts) {
		Map<Double, Pair<Integer, Integer>> ret = new HashMap<Double, Pair<Integer, Integer>>();

		Double totalMentions = 0.0d;

		int birthYear = Collections.min(yearCounts.keySet());
		int deathYear = Collections.max(yearCounts.keySet());

		for (int year : yearCounts.keySet())
			totalMentions += yearCounts.get(year);

		if (totalMentions == 0) {
			ret.put(-1.0d, new ImmutablePair<Integer, Integer>(0, 0));
			return ret;
		}

		for (Double threshold : thresholds) {

			Integer minInterval = Integer.MAX_VALUE; // deathYear - birthYear +
														// 1;
			Integer minLeft = -1, minRight = -1;

			Integer left = birthYear, right = birthYear - 1;
			double aggSum = 0;

			while (true) {
				if (aggSum / totalMentions < threshold) {
					++right;

					if (right > deathYear)
						break;

					Double count = yearCounts.get(right);
					if (null == count)
						count = 0.0d;
					aggSum += count;
				} else {
					Integer thisInterval = right - left + 1;

					if (thisInterval < minInterval) {
						minInterval = thisInterval;
						minLeft = left;
						minRight = right;
					}

					Double count = yearCounts.get(left);
					if (null != count)
						aggSum -= count;

					++left;
				}
			}

			if (minLeft != -1) {
				ret.put(threshold, new ImmutablePair<Integer, Integer>(minLeft,
						minRight));
			}
		}

		return ret;
	}

	public static Map<String, List<WeigthtedActivity>> getMostWeightedPeriods(
			Map<Integer, Double> yearCounts) {

		java.util.Map<String, List<WeigthtedActivity>> weightedPeriods = new HashMap<String, List<WeigthtedActivity>>();

		for (String weightMethod : weightFuncs.keySet())
			weightedPeriods.put(
					weightMethod,
					weightedPeriodByMethod(yearCounts,
							weightFuncs.get(weightMethod)));

		return weightedPeriods;
	}

	private static List<WeigthtedActivity> weightedPeriodByMethod(
			Map<Integer, Double> yearCounts, WeightFromPeriodCount weightFunc) {
		{
			int birthYear = Collections.min(yearCounts.keySet());
			int deathYear = Collections.max(yearCounts.keySet());

			List<WeigthtedActivity> ret = new ArrayList<WeigthtedActivity>();

			Double[] mentions = new Double[deathYear - birthYear + 1];
			Double[] cumMentions = new Double[deathYear - birthYear + 1];
			Double totalMentions = 0.0d;

			for (int year = birthYear; year < deathYear + 1; ++year) {
				int idx = year - birthYear;
				mentions[idx] = yearCounts.containsKey(year) ? yearCounts
						.get(year) : 0;

				cumMentions[idx] = 0.0d;
				totalMentions += mentions[idx];
			}

			for (int gap = 0; gap < deathYear - birthYear; ++gap) {
				double maxRatio = 0;
				int maxRatioYearBegin = -1;
				int maxRatioYearEnd = -1;

				for (int year = birthYear; year < deathYear - gap + 1; ++year) {
					int idx = year - birthYear;

					cumMentions[idx] += mentions[idx + gap];

					double ratio = (double) cumMentions[idx]
							* weightFunc.getWeight(year, year + gap + 1,
									cumMentions[idx], totalMentions);

					if (ratio > maxRatio) {
						maxRatio = ratio;
						maxRatioYearBegin = birthYear + idx;
						maxRatioYearEnd = maxRatioYearBegin + gap;
					}
				}

				// we now have the highest mentions ratio for this gap period
				if (maxRatio > 0) {
					ret.add(new WeigthtedActivity(maxRatio, gap,
							maxRatioYearBegin, maxRatioYearEnd));
				}
			}

			Collections.sort(ret);
			Collections.reverse(ret);

			// TODO: hardcoded top 3 ratioed periods
			if (!ret.isEmpty())
				return ret.subList(0, Math.min(3, ret.size()));

			return null;
		}
	}

	public static final Set<String> StopwordSet = new HashSet<String>();

	public static final String[] STOPWORDS = { "a", "able", "about", "above",
			"abst", "accordance", "according", "accordingly", "across", "act",
			"actually", "added", "adj", "adopted", "affected", "affecting",
			"affects", "after", "afterwards", "again", "against", "ah", "all",
			"almost", "alone", "along", "already", "also", "although",
			"always", "am", "among", "amongst", "an", "and", "announce",
			"another", "any", "anybody", "anyhow", "anymore", "anyone",
			"anything", "anyway", "anyways", "anywhere", "apparently",
			"approximately", "are", "aren", "arent", "arise", "around", "as",
			"aside", "ask", "asking", "at", "auth", "available", "away",
			"awfully", "b", "back", "be", "became", "because", "become",
			"becomes", "becoming", "been", "before", "beforehand", "begin",
			"beginning", "beginnings", "begins", "behind", "being", "believe",
			"below", "beside", "besides", "between", "beyond", "biol", "both",
			"brief", "briefly", "but", "by", "c", "ca", "came", "can",
			"cannot", "can't", "cause", "causes", "certain", "certainly", "co",
			"com", "come", "comes", "contain", "containing", "contains",
			"could", "couldnt", "d", "date", "did", "didn't", "different",
			"do", "does", "doesn't", "doing", "done", "don't", "down",
			"downwards", "due", "during", "e", "each", "ed", "edu", "effect",
			"eg", "eight", "eighty", "either", "else", "elsewhere", "end",
			"ending", "enough", "especially", "et", "et-al", "etc", "even",
			"ever", "every", "everybody", "everyone", "everything",
			"everywhere", "ex", "except", "f", "far", "few", "ff", "fifth",
			"first", "five", "fix", "followed", "following", "follows", "for",
			"former", "formerly", "forth", "found", "four", "from", "further",
			"furthermore", "g", "gave", "get", "gets", "getting", "give",
			"given", "gives", "giving", "go", "goes", "gone", "got", "gotten",
			"h", "had", "happens", "hardly", "has", "hasn't", "have",
			"haven't", "having", "he", "hed", "hence", "her", "here",
			"hereafter", "hereby", "herein", "heres", "hereupon", "hers",
			"herself", "hes", "hi", "hid", "him", "himself", "his", "hither",
			"home", "how", "howbeit", "however", "hundred", "i", "id", "ie",
			"if", "i'll", "im", "immediate", "immediately", "importance",
			"important", "in", "inc", "indeed", "index", "information",
			"instead", "into", "invention", "inward", "is", "isn't", "it",
			"itd", "it'll", "its", "itself", "i've", "j", "just", "k", "keep",
			"keeps", "kept", "keys", "kg", "km", "know", "known", "knows", "l",
			"largely", "last", "lately", "later", "latter", "latterly",
			"least", "less", "lest", "let", "lets", "like", "liked", "likely",
			"line", "little", "'ll", "look", "looking", "looks", "ltd", "m",
			"made", "mainly", "make", "makes", "many", "may", "maybe", "me",
			"mean", "means", "meantime", "meanwhile", "merely", "mg", "might",
			"million", "miss", "ml", "more", "moreover", "most", "mostly",
			"mr", "mrs", "much", "mug", "must", "my", "myself", "n", "na",
			"name", "namely", "nay", "nd", "near", "nearly", "necessarily",
			"necessary", "need", "needs", "neither", "never", "nevertheless",
			"new", "next", "nine", "ninety", "no", "nobody", "non", "none",
			"nonetheless", "noone", "nor", "normally", "nos", "not", "noted",
			"nothing", "now", "nowhere", "o", "obtain", "obtained",
			"obviously", "of", "off", "often", "oh", "ok", "okay", "old",
			"omitted", "on", "once", "one", "ones", "only", "onto", "or",
			"ord", "other", "others", "otherwise", "ought", "our", "ours",
			"ourselves", "out", "outside", "over", "overall", "owing", "own",
			"p", "page", "pages", "part", "particular", "particularly", "past",
			"per", "perhaps", "placed", "please", "plus", "poorly", "possible",
			"possibly", "potentially", "pp", "predominantly", "present",
			"previously", "primarily", "probably", "promptly", "proud",
			"provides", "put", "q", "que", "quickly", "quite", "qv", "r",
			"ran", "rather", "rd", "re", "readily", "really", "recent",
			"recently", "ref", "refs", "regarding", "regardless", "regards",
			"related", "relatively", "research", "respectively", "resulted",
			"resulting", "results", "right", "run", "s", "said", "same", "saw",
			"say", "saying", "says", "sec", "section", "see", "seeing", "seem",
			"seemed", "seeming", "seems", "seen", "self", "selves", "sent",
			"seven", "several", "shall", "she", "shed", "she'll", "shes",
			"should", "shouldn't", "show", "showed", "shown", "showns",
			"shows", "significant", "significantly", "similar", "similarly",
			"since", "six", "slightly", "so", "some", "somebody", "somehow",
			"someone", "somethan", "something", "sometime", "sometimes",
			"somewhat", "somewhere", "soon", "sorry", "specifically",
			"specified", "specify", "specifying",
			/*
			 * "state", "states",
			 */
			"still", "stop", "strongly", "sub", "substantially",
			"successfully", "such", "sufficiently", "suggest", "sup", "sure",
			"t", "take", "taken", "taking", "tell", "tends", "th", "than",
			"thank", "thanks", "thanx", "that", "that'll", "thats", "that've",
			"the", "their", "theirs", "them", "themselves", "then", "thence",
			"there", "thereafter", "thereby", "thered", "therefore", "therein",
			"there'll", "thereof", "therere", "theres", "thereto", "thereupon",
			"there've", "these", "they", "theyd", "they'll", "theyre",
			"they've", "think", "this", "those", "thou", "though", "thoughh",
			"thousand", "throug", "through", "throughout", "thru", "thus",
			"til", "tip", "to", "together", "too", "took", "toward", "towards",
			"tried", "tries", "truly", "try", "trying", "ts", "twice", "two",
			"u", "un", "under", "unfortunately", "unless", "unlike",
			"unlikely", "until", "unto", "up", "upon", "ups", "us", "use",
			"used", "useful", "usefully", "usefulness", "uses", "using",
			"usually", "v", "value", "various", "'ve", "very", "via", "viz",
			"vol", "vols", "vs", "w", "want", "wants", "was", "wasn't", "way",
			"we", "wed", "welcome", "we'll", "went", "were", "weren't",
			"we've", "what", "whatever", "what'll", "whats", "when", "whence",
			"whenever", "where", "whereafter", "whereas", "whereby", "wherein",
			"wheres", "whereupon", "wherever", "whether", "which", "while",
			"whim", "whither", "who", "whod", "whoever", "whole", "who'll",
			"whom", "whomever", "whos", "whose", "why", "widely", "willing",
			"wish", "with", "within", "without", "won't", "words", "world",
			"would", "wouldn't", "www", "x", "y", "yes", "yet", "you", "youd",
			"you'll", "your", "youre", "yours", "yourself", "yourselves",
			"you've", "z", "zero", };

	static {
		// weightFuncs.put("1", new WeightFromPeriodCount() {
		// @Override
		// public double getWeight(int beginYear, int endYear, int mentions,
		// int totalMentions) {
		// return 1;
		// }
		// });
		// weightFuncs.put("k", new WeightFromPeriodCount() {
		// @Override
		// public double getWeight(int beginYear, int endYear, int mentions,
		// int totalMentions) {
		// return 1.0d / (double) (endYear - beginYear);
		// }
		// });
		weightFuncs.put("sqrt(k)", new WeightFromPeriodCount() {
			@Override
			public double getWeight(int beginYear, int endYear,
					double mentions, double totalMentions) {
				return 1.0d / Math.sqrt(endYear - beginYear);
			}
		});
		weightFuncs.put("k^1/E", new WeightFromPeriodCount() {
			@Override
			public double getWeight(int beginYear, int endYear,
					double mentions, double totalMentions) {
				return 1.0d / Math.pow(endYear - beginYear, 1.0d / Math.E);
			}
		});
		weightFuncs.put("k^1/phi", new WeightFromPeriodCount() {
			@Override
			public double getWeight(int beginYear, int endYear,
					double mentions, double totalMentions) {
				return 1.0d / Math.pow(endYear - beginYear, 1.0d / 1.61803399);
			}
		});
		// weightFuncs.put("frac * period", new WeightFromPeriodCount() {
		// @Override
		// public double getWeight(int beginYear, int endYear, int mentions,
		// int totalMentions) {
		// return (double) (endYear - beginYear) * (double) (mentions) /
		// (double) (totalMentions);
		// }
		// });

		try {
			Scanner s = new Scanner(new File(
					"/home/sandesh247/year_count_weights"));

			while (s.hasNextLine()) {
				String line = s.nextLine();
				String[] parts = StringUtils.split(line);

				yearWeights.put(Integer.parseInt(parts[0]),
						Double.parseDouble(parts[1]));
			}
		} catch (FileNotFoundException e) {
			// throw new RuntimeException(e);
		}

		for (String word : STOPWORDS) {
			StopwordSet.add(word);
		}
	}

}
